package com.spc.car.web.log;

import com.baomidou.mybatisplus.core.toolkit.BeanUtils;
import com.spc.car.common.utils.http.HttpUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.IntStream;

public class BaseInterceptor {

    public Map<String, Object> getArgs(ProceedingJoinPoint joinPoint, Method currentMethod) {
        Object[] args = joinPoint.getArgs();

        Map<String, Object> params = new HashMap<>();
        params.put("HttpServletRequest", HttpUtils.getRequestParameters());

        Map<String, Object> methodParams = new HashMap<>();

        Map<Integer, String> paramIndexNameMap = getParamNameMaps(currentMethod);

        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            if (keepIt(arg)) {
                String paramName = paramIndexNameMap.get(i);
                //String paramName = getParameterNameByIndex(currentMethod, parameterNames, parameters, i);
                methodParams.put(paramName, arg);
            }
        }

        params.put(currentMethod.getName(), methodParams);
        return params;
    }

    public Map<String, Object> convertArgsToMap(ProceedingJoinPoint joinPoint, Method currentMethod) {
        Object[] args = joinPoint.getArgs();

        Map<String, Object> methodParams = new HashMap<>();
        Map<Integer, String> paramIndexNameMap = getParamNameMaps(currentMethod);

        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            if (keepIt(arg)) {
                String paramName = paramIndexNameMap.get(i);
                if ("body".equals(paramName)) {
                   methodParams.putAll(BeanUtils.beanToMap(arg));
                } else if (arg instanceof MultipartFile) {
                   methodParams.put(paramName, ((MultipartFile)arg).getName());
                } else {
                        methodParams.put(paramName, arg);
                }
            }
        }
        return methodParams;
    }

    public boolean canToJson(Object[] objs) {
        boolean result = true;

        if (ArrayUtils.isNotEmpty(objs)) {
            for (Object obj : objs) {
                if (obj instanceof DataBinder || obj instanceof WebDataBinder) {
                    return false;
                }
            }
        }

        return result;
    }

    public boolean keepIt(Object obj) {
        return (!(obj instanceof BindingResult)
                && !(obj instanceof ModelMap)
                && !(obj instanceof Model)
                && !(obj instanceof HttpServletRequest)
                && !(obj instanceof HttpSession)
                && !(obj instanceof HttpServletResponse));
    }

    public Map<Integer, String> getParamNameMaps(Method currentMethod) {

        Parameter[] parameters = currentMethod.getParameters();

        Map<Integer, String> paramNameMap = new HashMap<>();

        if(ArrayUtils.isNotEmpty(parameters)) {
            IntStream.range(0, parameters.length).forEach(idx -> {
                Parameter a = parameters[idx];

                String paramName = a.getName();
                if(a.getName().startsWith("arg")) {

                    if (a.isAnnotationPresent(RequestParam.class)) {
                        paramName = a.getAnnotation(RequestParam.class).value();
                    }

                    if (a.isAnnotationPresent(PathVariable.class)) {
                        paramName = a.getAnnotation(PathVariable.class).value();
                    }

                    if (a.isAnnotationPresent(RequestBody.class)) {
                        paramName = "body";
                    }
                }

                paramNameMap.put(idx, paramName);
            });
        }
        //log.info("method param names: {}", paramNameMap);

        return paramNameMap;
    }

    public String toMessage(String spelMsgTemplate, ProceedingJoinPoint joinPoint, Object principal) throws Exception{

        ExpressionParser parser = new SpelExpressionParser();

        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        String[] params = discoverer.getParameterNames(method);

        Object[] args = joinPoint.getArgs();

        EvaluationContext context = new StandardEvaluationContext();
        for (int len = 0; len < params.length; len++) {
            context.setVariable(params[len], args[len]);
        }
        if (Objects.nonNull(principal)) {
            context.setVariable("principal", principal);
        }

        return parser
                .parseExpression(spelMsgTemplate, new TemplateParserContext())
                .getValue(context, String.class);
    }

    public Method getCurrentMethod(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Object target = joinPoint.getTarget();
        return target.getClass().getMethod(methodSignature.getName(),
                methodSignature.getParameterTypes());
    }


}
